package org.biloxi.game;

import java.util.LinkedList;
import java.util.List;

import org.biloxi.commands.Command;
import org.biloxi.commands.CommandRepository;
import org.biloxi.commands.Listener;
import org.biloxi.commands.changephase.ChangePhase;
import org.biloxi.commands.changeturn.ChangeTurn;
import org.biloxi.commands.endgame.EndGame;
import org.biloxi.commands.endgame.EndGame.Options;
import org.biloxi.commands.endgame.EndGame.Result;
import org.biloxi.commands.exceptions.CommandOverriddenException;
import org.biloxi.commands.impl.CommandRepositoryImpl;
import org.biloxi.game.gamerules.EndGameRule;
import org.biloxi.game.gamerules.NextPhaseRules;
import org.biloxi.game.gamerules.phaserules.PhaseRule;
import org.biloxi.game.gamerules.playerlost.Milled;
import org.biloxi.model.GameState;
import org.biloxi.model.GameState.Phase;
import org.biloxi.model.impl.GameStateImpl;
import org.biloxi.model.magicobjects.MagicObject;
import org.biloxi.model.magicobjects.MagicObjectRegistry;
import org.biloxi.model.mutable.GameStateMutable;
import org.biloxi.player.Player;
import org.biloxi.player.PlayerID;
import org.biloxi.player.actions.ActionOptionPair;

/**
 * Game engine in charge of starting a newGame, 
 *
 */
public class Game {
		
	private final List<Player> _players;
	private final GameStateMutable _g;
	private final CommandRepository _c;

	private final EndGameRule _endGameList;
	private List<PlayerID> _winnerids = null;
		
	public Game(List<Player> players) {
		_players = players;
		_c = new CommandRepositoryImpl(this);
		
		List<PlayerID> pids = new LinkedList<PlayerID>();
		List<List<MagicObject>> mos = new LinkedList<List<MagicObject>>();
		for(Player p : players){
			pids.add(p.getID());
			List<MagicObject> deck = new LinkedList<MagicObject>();
			for(String cardid : p.getDeck().cards)
				deck.add(MagicObjectRegistry.inst().createMagicObject(_c, cardid));
			mos.add(deck);
		}
		_g = new GameStateImpl(pids, mos,this);
		

		List<PlayerID> pls = new LinkedList<PlayerID>();
		for(Player p : _players)
			pls.add(p.getID());
		
		// Create Game Rules
		_endGameList = new EndGameRule(_c, pls);
		new Milled(_c);
		new PhaseRule(_c);
		new NextPhaseRules(_c);

		// Attach Game Rules
		_c.endGame().do_(
			new Listener<EndGame.Options,EndGame.Result>(){
				public void handle(Command<Options, Result> cmd, Options options, Result result, GameState g) {
					_winnerids = result.winnerids;
				}
			}
		);
	}
	
	public GameState getGameState(){
		return _g;
	}
	
	public List<Player> getPlayers() {
		return _players;
	}
		
	public void init(){}
	
	protected boolean gameEnded(){
		return _winnerids != null;
	}
	protected boolean isLegal(ActionOptionPair<?> ao, PlayerID pid){
		return ao.isLegal(_g, _g.getPlayerState(pid));
	}
	
	public ActionOptionPair<?>[] requestPlayerAction(PlayerID pid){
		for(Player p : _players){
			if(p.getID() == pid){
				return p.getAction(_g,_g.getPlayerState(pid));
			}
		}
		return null;
	}
		
	//loop turns as well as phases until the game ends
	public void run(){

		_g.setTurnsPlayerID(getPlayers().get(0).getID());
		_c.changePhase( new ChangePhase.Options(Phase.BOT) );
		
		int nextPlayerIndex = 0;
		while(!gameEnded()){
			boolean playerSkipped = true;
			do{
				//the next player is allPlayers.get(currentPlayerid+1), but we need to mod it by the size of allPlayers to wrap it around from the last player to the first player
				nextPlayerIndex = (_g.getPlayerIDs().indexOf(_g.getTurnsPlayerID())+1) % _g.getPlayerIDs().size();
				PlayerID nextPid = _g.getPlayerIDs().get(nextPlayerIndex);
				
				try{
					//Note, Since this uses hardcoded values if the enum in Phase changes this may have to!	
					_c.changeTurn( new ChangeTurn.Options(nextPid) );
					playerSkipped = false;
				}catch(CommandOverriddenException coe){
					playerSkipped = true;
				}
			}while(playerSkipped);

			_c.changePhase( new ChangePhase.Options(Phase.BOT) );
		}
		
		System.out.println("Game Winners: ");
		for(PlayerID p : _winnerids)
			System.out.println("\t"+p);
	}
	
}
